﻿using Abp.AspNetCore;
using Abp.Extensions;
using Abp.Runtime.Session;
using AspectCore.Configuration;
using AspectCore.DynamicProxy;
using AspectCore.Extensions.Windsor;
using DotNetCore.CAP;
using DotNetCore.CAP.Dashboard;
using EasyCaching.Core.Internal;
using EasyCaching.InMemory;
using EasyCaching.Interceptor.AspectCore;
using FluentFTP;
using Hangfire;
using Hangfire.Console;
using Hangfire.Dashboard.BasicAuthorization;
using Hangfire.Heartbeat;
using Hangfire.Heartbeat.Server;
using Hangfire.MySql.Core;
using Hicap.QRCodeWarehouse.Core;
using Hicap.QRCodeWarehouse.Core.Common;
using Hicap.QRCodeWarehouse.Core.Consul;
using Hicap.QRCodeWarehouse.Core.EFCore;
using Hicap.QRCodeWarehouse.Core.Extensions;
using Hicap.QRCodeWarehouse.Core.Identity;
using Hicap.QRCodeWarehouse.Core.Interceptor;
using Hicap.QRCodeWarehouse.Core.Managers;
using Hicap.QRCodeWarehouse.Core.Notify;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc.Cors.Internal;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using MongoDB.Driver;
using Serilog;
using Serilog.Events;
using Swashbuckle.AspNetCore.Swagger;
using System;
using System.Collections.Generic;
using System.IdentityModel.Tokens.Jwt;
using System.IO;
using System.Linq;
using System.Net;
using LoggerConfiguration = Serilog.LoggerConfiguration;

namespace Hicap.QRCodeWarehouse.WebApi
{
    /// <summary>
    /// Startup
    /// </summary>
    /// <remarks>
    /// Reference
    /// ABP Framework:https://aspnetboilerplate.com/
    /// EasyCacheing:https://github.com/dotnetcore/EasyCaching
    /// AspectCore:https://github.com/dotnetcore/AspectCore-Framework
    /// FluentFtp:https://github.com/robinrodricks/FluentFTP
    /// Netcore 2.1:https://www.microsoft.com/net
    /// AutoMapper:http://automapper.org/
    /// CAP:https://www.github.com/dotnetcore/cap
    /// Mongodb C# Driver:https://docs.mongodb.com/ecosystem/drivers/csharp/
    /// HangFire:https://www.hangfire.io/
    /// SeriLog:https://serilog.net/
    /// ...more references in nuget installed
    /// </remarks>
    public class Startup
    {
        private const string DefaultCorsPolicyName = "default";
        private IConfiguration Configuration { get; }
        private string MongoConnectionString { get; }
        private string MySqlConnectionString { get; }

        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
            //            MongoConnectionString = Configuration.GetConnectionString("Hicap.QRCodeWarehouse.Processor");
            MySqlConnectionString = Configuration.GetConnectionString(
                "Hicap.QRCodeWarehouse");
        }

        /// <summary>
        /// Configure services
        /// </summary>
        /// <param name="services">IServiceCollection</param>
        /// <returns></returns>
        public IServiceProvider ConfigureServices(IServiceCollection services)
        {
            //Serilog
            AddLogging(services);

            //Consul
            AddConsul(services);

            //IdentityServer4 authentication
            AddAuthentication(services);

            //Cap
            AddCap(services);

            //SignalR
            AddSignalR(services);

            //Cors
            AddCors(services);

            //Mvc Filter
            AddMvc(services);

            //Hangfire
            AddHangfire(services);

            //Default DbContext
            AddDbContext(services);

            //UnitOfWork
            AddUnitOfWork(services);

            //Swagger
            AddSwaggerGen(services);

            //EasyCaching in memory
            AddDefaultInMemoryCache(services);

            //Fluent FTP Client
            AddFtpClient(services);

            //ABP module and make controllers for appServices
            return AddAbp(services);
        }

        /// <summary>
        /// Configure Consul
        /// </summary>
        /// <param name="services">IServiceCollection</param>
        private void AddConsul(IServiceCollection services)
        {
            services.AddConsul(x => x.WithHost($"http://{Configuration["ConsulConfiguration:Host"]}:{Configuration["ConsulConfiguration:Port"]}"));
        }

        /// <summary>
        /// Configure FtpClient
        /// </summary>
        /// <param name="services">IServiceCollection</param>
        private void AddFtpClient(IServiceCollection services)
        {
            services.AddSingleton<FtpClient>(x => new FtpClient()
            {
                Host = Configuration["Ftp:HostAddress"],
                Port = Convert.ToInt32(Configuration["Ftp:Port"]),
                Credentials = new NetworkCredential(Configuration["DefaultAuthorizationUser:UserName"], Configuration["DefaultAuthorizationUser:Password"])
            });
        }

        /// <summary>
        /// Configure Hangfire
        /// </summary>
        /// <param name="services">IServiceCollection</param>
        /// <remarks>Use mysql storage,use console</remarks>
        private void AddHangfire(IServiceCollection services)
        {
            services.AddHangfire(config =>
            {
                config.UseHeartbeatPage(checkInterval: TimeSpan.FromSeconds(1));
                config.UseConsole();
                config.UseStorage(new MySqlStorage(MySqlConnectionString));
            });
        }

        /// <summary>
        /// Configure SignalR
        /// </summary>
        /// <param name="services">IServiceCollection</param>
        private static void AddSignalR(IServiceCollection services)
        {
            services.AddSignalR();
        }

        /// <summary>
        /// Configure Serilog
        /// </summary>
        /// <param name="services">IServiceCollection</param>
        /// <remarks>
        ///output:
        /// 1.console
        /// 2.error.log
        /// 3.info.log
        /// </remarks>
        private void AddLogging(IServiceCollection services)
        {
            string logPth = Path.Combine(Directory.GetCurrentDirectory(), "logs");

            Serilog.Core.Logger logger = new LoggerConfiguration()
                .MinimumLevel.Debug()
                .MinimumLevel.Override("Microsoft", LogEventLevel.Information)
                .MinimumLevel.Override("Microsoft.AspNetCore", LogEventLevel.Information)
                .MinimumLevel.Override("System", LogEventLevel.Information)
                .MinimumLevel.Override("DotNetCore", LogEventLevel.Information)
                .WriteTo.Console(restrictedToMinimumLevel: LogEventLevel.Debug)
                .WriteTo.File(Path.Combine(logPth, "info", "log-.log"), rollingInterval: RollingInterval.Day, restrictedToMinimumLevel: LogEventLevel.Information)
                .WriteTo.File(Path.Combine(logPth, "error", "log-.log"), rollingInterval: RollingInterval.Day,
                    restrictedToMinimumLevel: LogEventLevel.Warning).CreateLogger();

            Log.Logger = logger;

            services.AddLogging(x =>
            {
                x.SetMinimumLevel(LogLevel.Debug);
                x.AddSerilog(logger);
            });
        }

        /// <summary>
        /// Configure Abp
        /// </summary>
        /// <param name="services">IServiceCollection</param>
        /// <returns></returns>
        /// <remarks>
        /// abp ioc container use AspectCore interceptors
        /// 1.EasyCachingInterceptor
        /// 2.MethodExecuteLoggerInterceptor
        /// </remarks>
        private static IServiceProvider AddAbp(IServiceCollection services)
        {
            return services.AddAbp<QrCodeWarehouseWebApiModule>(o =>
            {
                o.IocManager.IocContainer.AddAspectCoreFacility(
                    c =>
                    {
                        c.Interceptors.AddTyped<EasyCachingInterceptor>(method =>
                            typeof(IEasyCaching).IsAssignableFrom(method.DeclaringType));
                        c.Interceptors.AddTyped<MethodExecuteLoggerInterceptor>(method =>
                        {
                            bool flag = method.DeclaringType.Assembly.FullName.StartsWith(SysConsts.SysName);

                            if (method.IsConstructor)
                                flag = false;

                            if (method.IsProxy() && method.DeclaringType.IsProxy())
                                flag = false;

                            if (method.DeclaringType.Name.EndsWith("Context"))
                                flag = false;

                            return flag;
                        });
                    });
            });
        }

        /// <summary>
        /// EasyCaching use memory cache
        /// </summary>
        /// <param name="services">IServiceCollection</param>
        private static void AddDefaultInMemoryCache(IServiceCollection services)
        {
            //MemoryCache
            services.AddDefaultInMemoryCache();
        }

        /// <summary>
        /// Configure Swagger
        /// </summary>
        /// <param name="services">IServiceCollection</param>
        private static void AddSwaggerGen(IServiceCollection services)
        {
            services.AddSwaggerGen(c =>
            {
                c.SwaggerDoc("v1", new Info { Title = "Hicap.QRCodeWarhouse API", Version = "v1" });
                c.DocInclusionPredicate((docName, description) => true);
            });
        }

        /// <summary>
        /// Configure Authentication with IdentityServer4 remote
        /// </summary>
        /// <param name="services">IServiceCollection</param>
        private void AddAuthentication(IServiceCollection services)
        {
            JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();

            services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
                .AddIdentityServerAuthentication(options =>
                {
                    options.RequireHttpsMetadata = false; // for dev env
                    options.Authority =
                        $"http://{Configuration["Identity:Host"]}:{Configuration["Identity:Port"]}";
                    options.ApiName = SysConsts.SysName; // match with
                    options.ApiSecret = SysConsts.TokenSecurityKey;
                    //                    options.ApiName = SysConsts.IdentityServerApiName; // match with
                    //                    options.ApiSecret = SysConsts.IdentityServerTokenSecurityKey;
                    options.TokenRetriever = CustomTokenRetriever.FromHeaderAndQueryString;
                    options.Validate(JwtBearerDefaults.AuthenticationScheme);
                });
        }

        /// <summary>
        /// EFCore UnitOfWork
        /// </summary>
        /// <param name="services">IServiceCollection</param>
        private static void AddUnitOfWork(IServiceCollection services)
        {
            services.AddUnitOfWork<WarehouseDbContext>();
        }

        /// <summary>
        /// EFCore DbContext
        /// </summary>
        /// <param name="services">IServiceCollection</param>
        private void AddDbContext(IServiceCollection services)
        {
            services.AddSingleton(typeof(IEnumerable<DbContextOptions>), new List<DbContextOptions>() { (new DbContextOptionsBuilder<WarehouseDbContext>()).Options });

            //            services.AddDbContext<WarehouseDbContext>(ServiceLifetime.Transient);

            services.AddDbContextPool<WarehouseDbContext>(options =>
            {
                //                options.UseMySql(MySqlConnectionString);
                WarehouseDbContextConfigurer.Configure(options, MySqlConnectionString);
            });
        }

        /// <summary>
        /// Configure CORS
        /// </summary>
        /// <param name="services">IServiceCollection</param>
        private void AddCors(IServiceCollection services)
        {
            services.AddCors(
                //                options => options.AddDefaultPolicy(option => option
                options =>
                {
                    options.AddDefaultPolicy(x => x.AllowAnyHeader().AllowAnyMethod().AllowAnyOrigin().AllowCredentials());
                    options.AddPolicy(
                        DefaultCorsPolicyName,
                        builder => builder
                            .WithOrigins(
                                Configuration["App:CorsOrigins"]
                                    .Split(",", StringSplitOptions.RemoveEmptyEntries)
                                    .Select(o => o.RemovePostFix("/"))
                                    .ToArray()
                            )
                            .AllowCredentials()
                            .AllowAnyHeader()
                            .AllowAnyMethod()
                    );
                });
        }

        /// <summary>
        /// Configure Filter
        /// </summary>
        /// <param name="services">IServiceCollection</param>
        private static void AddMvc(IServiceCollection services)
        {
            services.AddMvcCore(options =>
            {
                options.Filters.Add(new CorsAuthorizationFilterFactory(DefaultCorsPolicyName));
                options.Filters.Add(new HicapAuthorizationFilter());
            }).AddApiExplorer().AddAuthorization().AddJsonFormatters();
        }

        /// <summary>
        /// Configure Cap
        /// </summary>
        /// <param name="services">IServiceCollection</param>
        /// <remarks>Use mySql and rabbitMq</remarks>
        private void AddCap(IServiceCollection services)
        {
            services.AddSingleton<NotifyHub>();
            services.AddScoped<IMongoClient>(x => new MongoClient(MongoConnectionString));

            services.AddTransient<IProductManager, ProductManager>();
            services.AddTransient<IGenerationRuleManager, GenerationRuleManager>();
            services.AddTransient<IGenerationTaskManager, GenerationTaskManager>();
            services.AddTransient<IFileRepositoryManager, FileRepositoryManager>();
            services.AddTransient<IMonitorManager, MonitorManager>();

            services.AddScoped<IAbpSession, HicapSession>();
            services.AddScoped<IPrincipalAccessor, HicapPrincipalAccessor>();
            services.AddScoped<IEnumerable<ICapSubscribe>>(
            x =>
            {
                List<ICapSubscribe> list = new List<ICapSubscribe>()
                    {
                        services.BuildServiceProvider().GetService<IGenerationTaskManager>(),
                        services.BuildServiceProvider().GetService<IFileRepositoryManager>(),
                        services.BuildServiceProvider().GetService<IMonitorManager>()
                    };
                return list;
            });

            services.AddCap(x =>
            {
                x.SucceedMessageExpiredAfter = 12 * 3600;
                x.FailedRetryCount = 3;
                x.FailedRetryInterval = 60;
                x.UseDashboard(y => y.Authorization = new List<IDashboardAuthorizationFilter>()
                {
                    new CapAuthorizationFilter()
                    {
                        LoginCaseSensitive = true,
                        Users = new []
                        {
                            new BasicAuthAuthorizationUser
                            {
                                Login = Configuration["DefaultAuthorizationUser:UserName"],
                                PasswordClear =  Configuration["DefaultAuthorizationUser:Password"],
                            }
                        }
                    }
                });
                x.UseDiscovery(y =>
                {
                    string sysServiceName = GetServiceName(Configuration);
                    int sysServicePort = GetServicePort(Configuration);

                    int nodeId = Convert.ToInt32(sysServiceName.Substring(sysServiceName.Length - 1)) + 300;

                    y.DiscoveryServerHostName = Configuration["ConsulConfiguration:Host"];
                    y.DiscoveryServerPort = Convert.ToInt32(Configuration["ConsulConfiguration:Port"]);
                    y.CurrentNodeHostName = sysServiceName;
                    y.CurrentNodePort = sysServicePort;
                    y.NodeId = nodeId;
                    y.NodeName = $"Hicap Node - {sysServiceName}";
                });
                //                x.UseMongoDB(MongoConnectionString);
                x.UseMySql(MySqlConnectionString);
                x.UseRabbitMQ(y =>
                {
                    y.HostName = Configuration["RabbitMq:Host"];
                    y.Port = Convert.ToInt32(Configuration["RabbitMq:Port"]);
                    y.UserName = Configuration["RabbitMq:UserName"];
                    y.Password = Configuration["RabbitMq:Pwd"];
                    y.QueueMessageExpires = 259200000;
                });

                x.FailedThresholdCallback = (type, topic, content) =>
                {
                    Log.Error($"!-- CAP Message Callback ERROR -- [{type.ToString()}][{topic}][{content}]");
                };
            });
        }

        /// <summary>
        /// Bootstrap Configure
        /// </summary>
        /// <param name="app">IApplicationBuilder</param>
        /// <param name="lifetime">IApplicationLifetime</param>
        /// <param name="env">IHostingEnvironment</param>
        public void Configure(IApplicationBuilder app, IApplicationLifetime lifetime, IHostingEnvironment env)
        {
            try
            {
                app.UseAbp(option =>
                {
                    option.UseAbpRequestLocalization = false;
                });
                if (env.IsDevelopment())
                {
                    app.UseDeveloperExceptionPage();
                }
                app.UseAuthentication();

                app.UseMvcWithDefaultRoute();

                app.UseSignalR(routes =>
                {
                    routes.MapHub<NotifyHub>("/Notify");
                });
                if (!env.IsProduction())
                {
                    app.UseCors(DefaultCorsPolicyName);
                }

                app.UseHangfireServer(additionalProcesses: new[] { new SystemMonitor(TimeSpan.FromSeconds(1)) });
                app.UseHangfireWindsorJobActivator();
                if (!env.IsProduction())
                {
                    app.UseHangfireDashboard("/hangfire", new Hangfire.DashboardOptions
                    {
                        Authorization = new[]
                        {
                            new BasicAuthAuthorizationFilter(new BasicAuthAuthorizationFilterOptions
                            {
                                RequireSsl = false,
                                SslRedirect = false,
                                LoginCaseSensitive = true,
                                Users = new[]
                                {
                                    new BasicAuthAuthorizationUser
                                    {
                                        Login = Configuration["DefaultAuthorizationUser:UserName"],
                                        PasswordClear = Configuration["DefaultAuthorizationUser:Password"],
                                    }
                                }
                            })
                        }
                    });

                    app.UseSwagger();
                    app.UseSwaggerUI(c =>
                    {
                        c.InjectJavascript("/swagger/ui/on-complete.js");
                        c.SwaggerEndpoint("/swagger/v1/swagger.json", "Hicap.QRCodeWarhouse API V1");
                    });
                }
                app.UseConsul(lifetime, option =>
                 {
                     string sysServiceName = GetServiceName(Configuration);
                     int sysServicePort = GetServicePort(Configuration);

                     option.WithSelfHost($"http://{sysServiceName}:{sysServicePort}");
                     option.WithServerName(sysServiceName);
                 });
                //Global jobs->1.Warehouse monitor
                app.UseGlobalJobs();
            }
            catch (Exception e)
            {
                Log.Logger.Fatal(e, "Boot Failed");
            }
        }

        private static string GetServiceName(IConfiguration configuration)
        {
            string sysServiceName = Environment.GetEnvironmentVariable("HicapServiceName");

            if (string.IsNullOrEmpty(sysServiceName))
                sysServiceName = configuration["SelfHost:Host"];
            return sysServiceName;
        }

        private static int GetServicePort(IConfiguration configuration)
        {
            string sysServicePort = Environment.GetEnvironmentVariable("HicapServicePort");

            if (string.IsNullOrEmpty(sysServicePort))
                sysServicePort = configuration["SelfHost:Port"];
            return Convert.ToInt32(sysServicePort);
        }
    }
}